using System.Diagnostics;
using Pgno = System.UInt32;

using u32 = System.UInt32;

namespace Community.CsharpSqlite
{
	using sqlite3_pcache = Sqlite3.PCache1;

	public partial class Sqlite3
	{
		/*
		** 2008 August 05
		**
		** The author disclaims copyright to this source code.  In place of
		** a legal notice, here is a blessing:
		**
		**    May you do good and not evil.
		**    May you find forgiveness for yourself and forgive others.
		**    May you share freely, never taking more than you give.
		**
		*************************************************************************
		** This file implements that page cache.
		*************************************************************************
		**  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
		**  C#-SQLite is an independent reimplementation of the SQLite software library
		**
		**  SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2
		**
		*************************************************************************
		*/
		//#include "sqliteInt.h"

		/*
		** A complete page cache is an instance of this structure.
		*/

		public class PCache
		{
			public PgHdr pDirty, pDirtyTail;           /* List of dirty pages in LRU order */
			public PgHdr pSynced;                      /* Last synced page in dirty page list */
			public int _nRef;                           /* Number of referenced pages */
			public int nMax;                           /* Configured cache size */
			public int szPage;                         /* Size of every page in this cache */
			public int szExtra;                        /* Size of extra space for each page */
			public bool bPurgeable;                    /* True if pages are on backing store */
			public dxStress xStress; //int (*xStress)(void*,PgHdr*);       /* Call to try make a page clean */
			public object pStress;                     /* Argument to xStress */
			public sqlite3_pcache pCache;              /* Pluggable cache module */
			public PgHdr pPage1;                       /* Reference to page 1 */

			public int nRef                            /* Number of referenced pages */
			{
				get
				{
					return _nRef;
				}
				set
				{
					_nRef = value;
				}
			}

			public void Clear()
			{
				pDirty = null;
				pDirtyTail = null;
				pSynced = null;
				nRef = 0;
			}
		};

		/*
		** Some of the Debug.Assert() macros in this code are too expensive to run
		** even during normal debugging.  Use them only rarely on long-running
		** tests.  Enable the expensive asserts using the
		** -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 compile-time option.
		*/
#if SQLITE_ENABLE_EXPENSIVE_ASSERT
//# define expensive_assert(X)  Debug.Assert(X)
static void expensive_assert( bool x ) { Debug.Assert( x ); }
#else
		//# define expensive_assert(X)
#endif

		/********************************** Linked List Management ********************/

#if !NDEBUG &&  SQLITE_ENABLE_EXPENSIVE_ASSERT
/*
** Check that the pCache.pSynced variable is set correctly. If it
** is not, either fail an Debug.Assert or return zero. Otherwise, return
** non-zero. This is only used in debugging builds, as follows:
**
**   expensive_assert( pcacheCheckSynced(pCache) );
*/
static int pcacheCheckSynced(PCache pCache){
PgHdr p ;
for(p=pCache.pDirtyTail; p!=pCache.pSynced; p=p.pDirtyPrev){
Debug.Assert( p.nRef !=0|| (p.flags&PGHDR_NEED_SYNC) !=0);
}
return (p==null || p.nRef!=0 || (p.flags&PGHDR_NEED_SYNC)==0)?1:0;
}
#endif //* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */

		/*
** Remove page pPage from the list of dirty pages.
*/

		private static void pcacheRemoveFromDirtyList(PgHdr pPage)
		{
			PCache p = pPage.pCache;

			Debug.Assert(pPage.pDirtyNext != null || pPage == p.pDirtyTail);
			Debug.Assert(pPage.pDirtyPrev != null || pPage == p.pDirty);

			/* Update the PCache1.pSynced variable if necessary. */
			if (p.pSynced == pPage)
			{
				PgHdr pSynced = pPage.pDirtyPrev;
				while (pSynced != null && (pSynced.flags & PGHDR_NEED_SYNC) != 0)
				{
					pSynced = pSynced.pDirtyPrev;
				}
				p.pSynced = pSynced;
			}

			if (pPage.pDirtyNext != null)
			{
				pPage.pDirtyNext.pDirtyPrev = pPage.pDirtyPrev;
			}
			else
			{
				Debug.Assert(pPage == p.pDirtyTail);
				p.pDirtyTail = pPage.pDirtyPrev;
			}
			if (pPage.pDirtyPrev != null)
			{
				pPage.pDirtyPrev.pDirtyNext = pPage.pDirtyNext;
			}
			else
			{
				Debug.Assert(pPage == p.pDirty);
				p.pDirty = pPage.pDirtyNext;
			}
			pPage.pDirtyNext = null;
			pPage.pDirtyPrev = null;

#if SQLITE_ENABLE_EXPENSIVE_ASSERT
expensive_assert( pcacheCheckSynced(p) );
#endif
		}

		/*
		** Add page pPage to the head of the dirty list (PCache1.pDirty is set to
		** pPage).
		*/

		private static void pcacheAddToDirtyList(PgHdr pPage)
		{
			PCache p = pPage.pCache;

			Debug.Assert(pPage.pDirtyNext == null && pPage.pDirtyPrev == null && p.pDirty != pPage);

			pPage.pDirtyNext = p.pDirty;
			if (pPage.pDirtyNext != null)
			{
				Debug.Assert(pPage.pDirtyNext.pDirtyPrev == null);
				pPage.pDirtyNext.pDirtyPrev = pPage;
			}
			p.pDirty = pPage;
			if (null == p.pDirtyTail)
			{
				p.pDirtyTail = pPage;
			}
			if (null == p.pSynced && 0 == (pPage.flags & PGHDR_NEED_SYNC))
			{
				p.pSynced = pPage;
			}
#if SQLITE_ENABLE_EXPENSIVE_ASSERT
expensive_assert( pcacheCheckSynced(p) );
#endif
		}

		/*
		** Wrapper around the pluggable caches xUnpin method. If the cache is
		** being used for an in-memory database, this function is a no-op.
		*/

		private static void pcacheUnpin(PgHdr p)
		{
			PCache pCache = p.pCache;
			if (pCache.bPurgeable)
			{
				if (p.pgno == 1)
				{
					pCache.pPage1 = null;
				}
				sqlite3GlobalConfig.pcache.xUnpin(pCache.pCache, p, false);
			}
		}

		/*************************************************** General Interfaces ******
		**
		** Initialize and shutdown the page cache subsystem. Neither of these
		** functions are threadsafe.
		*/

		private static int sqlite3PcacheInitialize()
		{
			if (sqlite3GlobalConfig.pcache.xInit == null)
			{
				/* IMPLEMENTATION-OF: R-26801-64137 If the xInit() method is NULL, then the
				** built-in default page cache is used instead of the application defined
				** page cache. */
				sqlite3PCacheSetDefault();
			}
			return sqlite3GlobalConfig.pcache.xInit(sqlite3GlobalConfig.pcache.pArg);
		}

		private static void sqlite3PcacheShutdown()
		{
			if (sqlite3GlobalConfig.pcache.xShutdown != null)
			{
				/* IMPLEMENTATION-OF: R-26000-56589 The xShutdown() method may be NULL. */
				sqlite3GlobalConfig.pcache.xShutdown(sqlite3GlobalConfig.pcache.pArg);
			}
		}

		/*
		** Return the size in bytes of a PCache object.
		*/

		private static int sqlite3PcacheSize()
		{
			return 4;
		}// sizeof( PCache ); }

		/*
		** Create a new PCache object. Storage space to hold the object
		** has already been allocated and is passed in as the p pointer.
		** The caller discovers how much space needs to be allocated by
		** calling sqlite3PcacheSize().
		*/

		private static void sqlite3PcacheOpen(
		int szPage,                  /* Size of every page */
		int szExtra,                 /* Extra space associated with each page */
		bool bPurgeable,             /* True if pages are on backing store */
		dxStress xStress,//int (*xStress)(void*,PgHdr*),/* Call to try to make pages clean */
		object pStress,              /* Argument to xStress */
		PCache p                     /* Preallocated space for the PCache */
		)
		{
			p.Clear();//memset(p, 0, sizeof(PCache));
			p.szPage = szPage;
			p.szExtra = szExtra;
			p.bPurgeable = bPurgeable;
			p.xStress = xStress;
			p.pStress = pStress;
			p.nMax = 100;
		}

		/*
		** Change the page size for PCache object. The caller must ensure that there
		** are no outstanding page references when this function is called.
		*/

		private static void sqlite3PcacheSetPageSize(PCache pCache, int szPage)
		{
			Debug.Assert(pCache.nRef == 0 && pCache.pDirty == null);
			if (pCache.pCache != null)
			{
				sqlite3GlobalConfig.pcache.xDestroy(ref pCache.pCache);
				pCache.pCache = null;
			}
			pCache.szPage = szPage;
		}

		/*
		** Try to obtain a page from the cache.
		*/

		private static int sqlite3PcacheFetch(
		PCache pCache,       /* Obtain the page from this cache */
		u32 pgno,            /* Page number to obtain */
		int createFlag,      /* If true, create page if it does not exist already */
		ref PgHdr ppPage     /* Write the page here */
		)
		{
			PgHdr pPage = null;
			int eCreate;

			Debug.Assert(pCache != null);
			Debug.Assert(createFlag == 1 || createFlag == 0);
			Debug.Assert(pgno > 0);

			/* If the pluggable cache (sqlite3_pcache*) has not been allocated,
			** allocate it now.
			*/
			if (null == pCache.pCache && createFlag != 0)
			{
				sqlite3_pcache p;
				int nByte;
				nByte = pCache.szPage + pCache.szExtra + 0;// sizeof( PgHdr );
				p = sqlite3GlobalConfig.pcache.xCreate(nByte, pCache.bPurgeable);
				//if ( null == p )
				//{
				//  return SQLITE_NOMEM;
				//}
				sqlite3GlobalConfig.pcache.xCachesize(p, pCache.nMax);
				pCache.pCache = p;
			}

			eCreate = createFlag * (1 + ((!pCache.bPurgeable || null == pCache.pDirty) ? 1 : 0));

			if (pCache.pCache != null)
			{
				pPage = sqlite3GlobalConfig.pcache.xFetch(pCache.pCache, pgno, eCreate);
			}

			if (null == pPage && eCreate == 1)
			{
				PgHdr pPg;

				/* Find a dirty page to write-out and recycle. First try to find a
				** page that does not require a journal-sync (one with PGHDR_NEED_SYNC
				** cleared), but if that is not possible settle for any other
				** unreferenced dirty page.
				*/
#if SQLITE_ENABLE_EXPENSIVE_ASSERT
expensive_assert( pcacheCheckSynced(pCache) );
#endif
				for (pPg = pCache.pSynced;
				pPg != null && (pPg.nRef != 0 || (pPg.flags & PGHDR_NEED_SYNC) != 0);
				pPg = pPg.pDirtyPrev
				)
					;
				pCache.pSynced = pPg;
				if (null == pPg)
				{
					for (pPg = pCache.pDirtyTail; pPg != null && pPg.nRef != 0; pPg = pPg.pDirtyPrev)
						;
				}
				if (pPg != null)
				{
					int rc;
#if SQLITE_LOG_CACHE_SPILL
      sqlite3_log(SQLITE_FULL,
                  "spill page %d making room for %d - cache used: %d/%d",
                  pPg->pgno, pgno,
                  sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache),
                  pCache->nMax);
#endif
					rc = pCache.xStress(pCache.pStress, pPg);
					if (rc != SQLITE_OK && rc != SQLITE_BUSY)
					{
						return rc;
					}
				}

				pPage = sqlite3GlobalConfig.pcache.xFetch(pCache.pCache, pgno, 2);
			}

			if (pPage != null)
			{
				if (null == pPage.pData)
				{
					//          memset(pPage, 0, sizeof(PgHdr));
					pPage.pData = sqlite3Malloc(pCache.szPage);//          pPage->pData = (void*)&pPage[1];
					//pPage->pExtra = (void*)&((char*)pPage->pData)[pCache->szPage];
					//memset(pPage->pExtra, 0, pCache->szExtra);
					pPage.pCache = pCache;
					pPage.pgno = pgno;
				}
				Debug.Assert(pPage.pCache == pCache);
				Debug.Assert(pPage.pgno == pgno);
				//assert(pPage->pData == (void*)&pPage[1]);
				//assert(pPage->pExtra == (void*)&((char*)&pPage[1])[pCache->szPage]);
				if (0 == pPage.nRef)
				{
					pCache.nRef++;
				}
				pPage.nRef++;
				if (pgno == 1)
				{
					pCache.pPage1 = pPage;
				}
			}
			ppPage = pPage;
			return (pPage == null && eCreate != 0) ? SQLITE_NOMEM : SQLITE_OK;
		}

		/*
		** Decrement the reference count on a page. If the page is clean and the
		** reference count drops to 0, then it is made elible for recycling.
		*/

		private static void sqlite3PcacheRelease(PgHdr p)
		{
			Debug.Assert(p.nRef > 0);
			p.nRef--;
			if (p.nRef == 0)
			{
				PCache pCache = p.pCache;
				pCache.nRef--;
				if ((p.flags & PGHDR_DIRTY) == 0)
				{
					pcacheUnpin(p);
				}
				else
				{
					/* Move the page to the head of the dirty list. */
					pcacheRemoveFromDirtyList(p);
					pcacheAddToDirtyList(p);
				}
			}
		}

		/*
		** Increase the reference count of a supplied page by 1.
		*/

		private static void sqlite3PcacheRef(PgHdr p)
		{
			Debug.Assert(p.nRef > 0);
			p.nRef++;
		}

		/*
		** Drop a page from the cache. There must be exactly one reference to the
		** page. This function deletes that reference, so after it returns the
		** page pointed to by p is invalid.
		*/

		private static void sqlite3PcacheDrop(PgHdr p)
		{
			PCache pCache;
			Debug.Assert(p.nRef == 1);
			if ((p.flags & PGHDR_DIRTY) != 0)
			{
				pcacheRemoveFromDirtyList(p);
			}
			pCache = p.pCache;
			pCache.nRef--;
			if (p.pgno == 1)
			{
				pCache.pPage1 = null;
			}
			sqlite3GlobalConfig.pcache.xUnpin(pCache.pCache, p, true);
		}

		/*
		** Make sure the page is marked as dirty. If it isn't dirty already,
		** make it so.
		*/

		private static void sqlite3PcacheMakeDirty(PgHdr p)
		{
			p.flags &= ~PGHDR_DONT_WRITE;
			Debug.Assert(p.nRef > 0);
			if (0 == (p.flags & PGHDR_DIRTY))
			{
				p.flags |= PGHDR_DIRTY;
				pcacheAddToDirtyList(p);
			}
		}

		/*
		** Make sure the page is marked as clean. If it isn't clean already,
		** make it so.
		*/

		private static void sqlite3PcacheMakeClean(PgHdr p)
		{
			if ((p.flags & PGHDR_DIRTY) != 0)
			{
				pcacheRemoveFromDirtyList(p);
				p.flags &= ~(PGHDR_DIRTY | PGHDR_NEED_SYNC);
				if (p.nRef == 0)
				{
					pcacheUnpin(p);
				}
			}
		}

		/*
		** Make every page in the cache clean.
		*/

		private static void sqlite3PcacheCleanAll(PCache pCache)
		{
			PgHdr p;
			while ((p = pCache.pDirty) != null)
			{
				sqlite3PcacheMakeClean(p);
			}
		}

		/*
		** Clear the PGHDR_NEED_SYNC flag from all dirty pages.
		*/

		private static void sqlite3PcacheClearSyncFlags(PCache pCache)
		{
			PgHdr p;
			for (p = pCache.pDirty; p != null; p = p.pDirtyNext)
			{
				p.flags &= ~PGHDR_NEED_SYNC;
			}
			pCache.pSynced = pCache.pDirtyTail;
		}

		/*
		** Change the page number of page p to newPgno.
		*/

		private static void sqlite3PcacheMove(PgHdr p, Pgno newPgno)
		{
			PCache pCache = p.pCache;
			Debug.Assert(p.nRef > 0);
			Debug.Assert(newPgno > 0);
			sqlite3GlobalConfig.pcache.xRekey(pCache.pCache, p, p.pgno, newPgno);
			p.pgno = newPgno;
			if ((p.flags & PGHDR_DIRTY) != 0 && (p.flags & PGHDR_NEED_SYNC) != 0)
			{
				pcacheRemoveFromDirtyList(p);
				pcacheAddToDirtyList(p);
			}
		}

		/*
		** Drop every cache entry whose page number is greater than "pgno". The
		** caller must ensure that there are no outstanding references to any pages
		** other than page 1 with a page number greater than pgno.
		**
		** If there is a reference to page 1 and the pgno parameter passed to this
		** function is 0, then the data area associated with page 1 is zeroed, but
		** the page object is not dropped.
		*/

		private static void sqlite3PcacheTruncate(PCache pCache, u32 pgno)
		{
			if (pCache.pCache != null)
			{
				PgHdr p;
				PgHdr pNext;
				for (p = pCache.pDirty; p != null; p = pNext)
				{
					pNext = p.pDirtyNext;
					/* This routine never gets call with a positive pgno except right
					** after sqlite3PcacheCleanAll().  So if there are dirty pages,
					** it must be that pgno==0.
					*/
					Debug.Assert(p.pgno > 0);
					if (ALWAYS(p.pgno > pgno))
					{
						Debug.Assert((p.flags & PGHDR_DIRTY) != 0);
						sqlite3PcacheMakeClean(p);
					}
				}
				if (pgno == 0 && pCache.pPage1 != null)
				{
					// memset( pCache.pPage1.pData, 0, pCache.szPage );
					pCache.pPage1.pData = sqlite3Malloc(pCache.szPage);
					pgno = 1;
				}
				sqlite3GlobalConfig.pcache.xTruncate(pCache.pCache, pgno + 1);
			}
		}

		/*
		** Close a cache.
		*/

		private static void sqlite3PcacheClose(PCache pCache)
		{
			if (pCache.pCache != null)
			{
				sqlite3GlobalConfig.pcache.xDestroy(ref pCache.pCache);
			}
		}

		/*
		** Discard the contents of the cache.
		*/

		private static void sqlite3PcacheClear(PCache pCache)
		{
			sqlite3PcacheTruncate(pCache, 0);
		}

		/*
		** Merge two lists of pages connected by pDirty and in pgno order.
		** Do not both fixing the pDirtyPrev pointers.
		*/

		private static PgHdr pcacheMergeDirtyList(PgHdr pA, PgHdr pB)
		{
			PgHdr result = new PgHdr();
			PgHdr pTail = result;
			while (pA != null && pB != null)
			{
				if (pA.pgno < pB.pgno)
				{
					pTail.pDirty = pA;
					pTail = pA;
					pA = pA.pDirty;
				}
				else
				{
					pTail.pDirty = pB;
					pTail = pB;
					pB = pB.pDirty;
				}
			}
			if (pA != null)
			{
				pTail.pDirty = pA;
			}
			else if (pB != null)
			{
				pTail.pDirty = pB;
			}
			else
			{
				pTail.pDirty = null;
			}
			return result.pDirty;
		}

		/*
		** Sort the list of pages in accending order by pgno.  Pages are
		** connected by pDirty pointers.  The pDirtyPrev pointers are
		** corrupted by this sort.
		**
		** Since there cannot be more than 2^31 distinct pages in a database,
		** there cannot be more than 31 buckets required by the merge sorter.
		** One extra bucket is added to catch overflow in case something
		** ever changes to make the previous sentence incorrect.
		*/

		//#define N_SORT_BUCKET  32
		private const int N_SORT_BUCKET = 32;

		private static PgHdr pcacheSortDirtyList(PgHdr pIn)
		{
			PgHdr[] a;
			PgHdr p;//a[N_SORT_BUCKET], p;
			int i;
			a = new PgHdr[N_SORT_BUCKET];//memset(a, 0, sizeof(a));
			while (pIn != null)
			{
				p = pIn;
				pIn = p.pDirty;
				p.pDirty = null;
				for (i = 0; ALWAYS(i < N_SORT_BUCKET - 1); i++)
				{
					if (a[i] == null)
					{
						a[i] = p;
						break;
					}
					else
					{
						p = pcacheMergeDirtyList(a[i], p);
						a[i] = null;
					}
				}
				if (NEVER(i == N_SORT_BUCKET - 1))
				{
					/* To get here, there need to be 2^(N_SORT_BUCKET) elements in
					** the input list.  But that is impossible.
					*/
					a[i] = pcacheMergeDirtyList(a[i], p);
				}
			}
			p = a[0];
			for (i = 1; i < N_SORT_BUCKET; i++)
			{
				p = pcacheMergeDirtyList(p, a[i]);
			}
			return p;
		}

		/*
		** Return a list of all dirty pages in the cache, sorted by page number.
		*/

		private static PgHdr sqlite3PcacheDirtyList(PCache pCache)
		{
			PgHdr p;
			for (p = pCache.pDirty; p != null; p = p.pDirtyNext)
			{
				p.pDirty = p.pDirtyNext;
			}
			return pcacheSortDirtyList(pCache.pDirty);
		}

		/*
		** Return the total number of referenced pages held by the cache.
		*/

		private static int sqlite3PcacheRefCount(PCache pCache)
		{
			return pCache.nRef;
		}

		/*
		** Return the number of references to the page supplied as an argument.
		*/

		private static int sqlite3PcachePageRefcount(PgHdr p)
		{
			return p.nRef;
		}

		/*
		** Return the total number of pages in the cache.
		*/

		private static int sqlite3PcachePagecount(PCache pCache)
		{
			int nPage = 0;
			if (pCache.pCache != null)
			{
				nPage = sqlite3GlobalConfig.pcache.xPagecount(pCache.pCache);
			}
			return nPage;
		}

#if SQLITE_TEST
    /*
** Get the suggested cache-size value.
*/
    static int sqlite3PcacheGetCachesize( PCache pCache )
    {
      return pCache.nMax;
    }
#endif

		/*
** Set the suggested cache-size value.
*/

		private static void sqlite3PcacheSetCachesize(PCache pCache, int mxPage)
		{
			pCache.nMax = mxPage;
			if (pCache.pCache != null)
			{
				sqlite3GlobalConfig.pcache.xCachesize(pCache.pCache, mxPage);
			}
		}

#if SQLITE_CHECK_PAGES  || (SQLITE_DEBUG)
		/*
** For all dirty pages currently in the cache, invoke the specified
** callback. This is only used if the SQLITE_CHECK_PAGES macro is
** defined.
*/

		private static void sqlite3PcacheIterateDirty(PCache pCache, dxIter xIter)
		{
			PgHdr pDirty;
			for (pDirty = pCache.pDirty; pDirty != null; pDirty = pDirty.pDirtyNext)
			{
				xIter(pDirty);
			}
		}

#endif
	}
}